using System;
using System.Reflection;
using Microsoft.Practices.ObjectBuilder2;
using Unity.Extensions;
using Xunit;

namespace Unity.Extensions
{
    public class VirtualMethodInterceptionReflectorTest
    {
        [Fact]
        public void CanAddHandlersInInheritedClass()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(DerivedWithAddedIntercepts).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<DerivedWithAddedIntercepts>(policies, new StubObjectFactory());
            IVirtualInterceptionPolicy policy = policies.Get<IVirtualInterceptionPolicy>(new NamedTypeBuildKey(typeof(DerivedWithAddedIntercepts)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(2, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][1]);
        }

        [Fact]
        public void InterceptionIsInherited()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(DerivedOneMethod).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<DerivedOneMethod>(policies, new StubObjectFactory());
            IVirtualInterceptionPolicy policy = policies.Get<IVirtualInterceptionPolicy>(new NamedTypeBuildKey(typeof(DerivedOneMethod)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(1, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
        }

        [Fact]
        public void NonPublicTypesNotCompatible()
        {
            PolicyList policies = new PolicyList();

            Assert.Throws<InvalidOperationException>(
                () => InterceptionReflector.Reflect<InternalClass>(policies, new StubObjectFactory()));
        }

        [Fact]
        public void NonVirtualMethodNotCompatible()
        {
            PolicyList policies = new PolicyList();

            Assert.Throws<InvalidOperationException>(
                () => InterceptionReflector.Reflect<NonVirtualMethod>(policies, new StubObjectFactory()));
        }

        [Fact]
        public void OneInterceptedMethod()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(OneMethod).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<OneMethod>(policies, new StubObjectFactory());
            IVirtualInterceptionPolicy policy = policies.Get<IVirtualInterceptionPolicy>(new NamedTypeBuildKey(typeof(OneMethod)));

            Assert.NotNull(policy);
            Assert.Equal(1, policy.Count);
            Assert.Equal(1, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
        }

        [Fact]
        public void ReflectShouldHappenOnGenericBaseClass()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(Generic<>).GetMethod("DoSomething");

            InterceptionReflector.Reflect<Generic<int>>(policies, new StubObjectFactory());
            IVirtualInterceptionPolicy policy = policies.Get<IVirtualInterceptionPolicy>(new NamedTypeBuildKey(typeof(Generic<>)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(1, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
        }

        [Fact]
        public void SealedTypeNotCompatible()
        {
            PolicyList policies = new PolicyList();

            Assert.Throws<InvalidOperationException>(
                () => InterceptionReflector.Reflect<SealedClass>(policies, new StubObjectFactory()));
        }

        [Fact]
        public void TwoAttributesOnOneMethod()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(OneMethodTwoAttributes).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<OneMethodTwoAttributes>(policies, new StubObjectFactory());
            IVirtualInterceptionPolicy policy = policies.Get<IVirtualInterceptionPolicy>(new NamedTypeBuildKey(typeof(OneMethodTwoAttributes)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(2, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][1]);
        }

        [Fact]
        public void TwoInterceptedMethods()
        {
            PolicyList policies = new PolicyList();
            MethodBase method1 = typeof(TwoMethods).GetMethod("InterceptedMethod1");
            MethodBase method2 = typeof(TwoMethods).GetMethod("InterceptedMethod2");

            InterceptionReflector.Reflect<TwoMethods>(policies, new StubObjectFactory());
            IVirtualInterceptionPolicy policy = policies.Get<IVirtualInterceptionPolicy>(new NamedTypeBuildKey(typeof(TwoMethods)));

            Assert.Equal(2, policy.Count);
            Assert.Equal(1, policy[method1].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method1][0]);
            Assert.Equal(1, policy[method2].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method2][0]);
        }

        [Fact]
        public void VirtualSealedMethodNotCompatible()
        {
            PolicyList policies = new PolicyList();

            Assert.Throws<InvalidOperationException>(
                () => InterceptionReflector.Reflect<VirtualSealed>(policies, new StubObjectFactory()));
        }

        public class DerivedOneMethod : OneMethod {}

        public class DerivedWithAddedIntercepts : OneMethod
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            public override void InterceptedMethod() {}
        }

        public class Generic<T>
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            public virtual void DoSomething(T data) {}
        }

        public class NonVirtualMethod
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            public void InterceptedMethod() {}
        }

        public class OneMethod
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            public virtual void InterceptedMethod() {}

            public void NonInterceptedMethod() {}
        }

        public class OneMethodTwoAttributes
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            [VirtualIntercept(typeof(RecordingHandler))]
            public virtual void InterceptedMethod() {}
        }

        public sealed class SealedClass : SealedClassBase {}

        public class SealedClassBase
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            public virtual void InterceptedMethod() {}
        }

        public class TwoMethods
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            public virtual void InterceptedMethod1() {}

            [VirtualIntercept(typeof(RecordingHandler))]
            public virtual void InterceptedMethod2() {}
        }

        public class VirtualSealed : OneMethod
        {
            public override sealed void InterceptedMethod() {}
        }

        internal class InternalClass
        {
            [VirtualIntercept(typeof(RecordingHandler))]
            public virtual void InterceptedMethod() {}
        }
    }
}